package org.railage.client;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Properties;

import org.apache.log4j.Logger;
import org.railage.common.dto.UserDTO;
import org.railage.common.protocol.Message;

/**
 * Client application class. Implements singleton pattern. Host and port of
 * ServerSocket are given in SETTINGS_INI file.
 * 
 * @author Cyril Fertikov
 */
public class Client {
	public static final String SETTINGS_INI = "settings.ini";

	private static final Logger log = Logger.getLogger(Client.class);

	private static Client instance = null;

	private String host = null;
	private int port = 0;

	private Socket socket = null;
	private ObjectOutputStream out = null;
	private ObjectInputStream in = null;
	private UserDTO currentUser = null;

	/**
	 * Initializes SETTINGS_INI file
	 */
	private Client() {
		Properties prop = new Properties();
		try {
			prop.load(this.getClass().getClassLoader()
					.getResourceAsStream(SETTINGS_INI));
			this.host = prop.getProperty("host");
			this.port = Integer.parseInt(prop.getProperty("port"));
		} catch (Exception e) {
			log.error("Error while reading settings.ini file");
			System.exit(1);
		}
	}

	/**
	 * Getting instance of client
	 * 
	 * @return Client instance.
	 */
	public static synchronized Client getInstance() {
		if (instance == null) {
			instance = new Client();
		}
		return instance;
	}

	/**
	 * Connects to ServerSocket
	 * 
	 * @throws UnknownHostException
	 * @throws IOException
	 */
	public void connect() throws UnknownHostException, IOException {
		try {
			socket = new Socket(host, port);
			out = new ObjectOutputStream(socket.getOutputStream());
			in = new ObjectInputStream(socket.getInputStream());
			log.info("Client connected to " + host + ":" + port);
		} catch (UnknownHostException e) {
			socket = null;
			throw e;
		} catch (IOException e) {
			socket = null;
			throw e;
		}
	}

	/**
	 * Checks whether client is connected to Server
	 * 
	 * @return true - if client is connected to server, false - otherwise.
	 */
	public boolean isConnected() {
		if (socket == null) {
			return false;
		} else {
			return true;
		}
	}

	/**
	 * Disconnects from ServerSocket
	 * 
	 */
	public void disconnect() {
		try {
			out.writeObject(new Message("disconnect", null));
			out.close();
			in.close();
			socket.close();
			log.info("Client disconnected from " + host + ":" + port);
		} catch (IOException e) {
			log.error("Exception while disconnecting from server:", e);
		} finally {
			socket = null;
		}
	}

	/**
	 * Calls method of Service class
	 * 
	 * @param method
	 *            Desired method.
	 * @param obj
	 *            Parameter.
	 * 
	 * @return Return value
	 * 
	 * @throws IOException
	 */
	public Object callMethod(String method, Object obj) throws IOException {
		if (!isConnected())
			throw new IOException("Socket is not connected");
		try {
			out.writeObject(new Message(method, obj));
			Message msg = (Message) in.readObject();
			return msg.getObj();
		} catch (IOException e) {
			socket = null;
			throw e;
		} catch (ClassNotFoundException e) {
			log.error("Exception while getting message from server:", e);
			return null;
		}
	}

	/**
	 * Getting User associated with connection.
	 * 
	 * @return UserDTO object.
	 */
	public UserDTO getCurrentUser() {
		return currentUser;
	}

	/**
	 * Setting User associated with connection.
	 * 
	 * @param currentUser
	 *            UserDTO object.
	 */
	public void setCurrentUser(UserDTO currentUser) {
		this.currentUser = currentUser;
	}

}
